package jeffaschenk.commons.container.security.web; import jeffaschenk.commons.container.security.AuthenticationException; import jeffaschenk.commons.container.security.constants.SecurityConstants; import jeffaschenk.commons.container.security.monitor.SecurityServiceMonitor; import jeffaschenk.commons.container.security.object.AuthenticationToken; import jeffaschenk.commons.container.security.object.SecuritySessionUserObject; import jeffaschenk.commons.util.StringUtils; import org.apache.commons.lang.LocaleUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.json.JSONException; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.MessageSource; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.web.RedirectStrategy; import org.springframework.security.web.authentication.*; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; import org.springframework.transaction.annotation.Transactional; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import javax.servlet.http.HttpSession; import java.io.IOException; import java.io.Writer; import java.util.Locale; /** * AuthenticationFilter Implemented authenticated Session * Authentication Filter. * <p/> * Spring Container application Context Aware to initialize and save Security * Session information for all Authenticated elements. * * @author jeffaschenk@gmail.com * @version $Id: $ */ public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter implements SecurityConstants { /** * Logging Constant <code>log</code> */ protected static Log log = LogFactory .getLog(AuthenticationFilter.class); /** * Injected Service Provider AppId */ @Value("#{systemEnvironmentProperties['serviceProvider.appId']}") private String serviceProviderAppId; /** * Injected Service Provider Secret */ @Value("#{systemEnvironmentProperties['serviceProvider.secret']}") private String serviceProviderSecret; /** * Injected Service Provider Graph Api Url */ @Value("#{systemEnvironmentProperties['serviceProvider.graph.apiUrl']}") private String serviceProviderGraphApiUrl; /** * Injected Service Provider Authorize Url */ @Value("#{systemEnvironmentProperties['serviceProvider.authorizeUrl']}") private String serviceProviderAuthorizeUrl; /** * Injected Service Provider Authorize Url */ @Value("#{systemEnvironmentProperties['serviceProvider.accessTokenUrl']}") private String serviceProviderAccessTokenUrl; /** * Injected Service Provider Redirect Url */ @Value("#{systemEnvironmentProperties['serviceProvider.redirectUrl']}") private String serviceProviderRedirectUrl; /** * Injected Service Provider Required Extended Permissions */ @Value("#{systemEnvironmentProperties['serviceProvider.extended.permissions']}") private String serviceProviderExtendedPermissions; @Value("#{systemEnvironmentProperties['serviceProvider.permission.dialog.form.default']}") private String serviceProviderPermissionDialogFormDefault; @Value("#{systemEnvironmentProperties['host']}") private String host; @Value("#{systemEnvironmentProperties['serviceProvider.deauthorization.callback']}") private String serviceProviderDeAuthorizationCallback; /** * Header Names */ private String usernameHeader = USERNAME_HeaderPropertyName; private String passwordHeader = PASSWORD_HeaderPropertyName; /** * Default Constructor */ public AuthenticationFilter() { super(SPRING_SECURITY_CHECK_URL_HOOK); } /** * Security Service Monitor */ private SecurityServiceMonitor securityServiceMonitor; public void setSecurityServiceMonitor(SecurityServiceMonitor securityServiceMonitor) { this.securityServiceMonitor = securityServiceMonitor; } /** * Autowired Services */ //@Autowired //private FacebookServiceProvider facebookServiceProvider; /** * Initialize the Singleton Session Cache. */ @PostConstruct public synchronized void init() { // ************************************ // Initialization log.info("AuthenticationFilter starting to Initialize."); // ********************************************* // Create a blank redirect strategy // to prevent Spring automatically // returning page content in the output stream. SavedRequestAwareAuthenticationSuccessHandler srh = new SavedRequestAwareAuthenticationSuccessHandler(); this.setAuthenticationSuccessHandler(srh); srh.setRedirectStrategy(new RedirectStrategy() { @Override public void sendRedirect(HttpServletRequest httpservletrequest, HttpServletResponse httpservletresponse, String s) throws IOException { //do nothing, no redirect } }); // *************************************** // Proceed with additional Initialization log.info("AuthenticationFilter has been Initialized"); } /** * <p/> * Destroy Implemented Spring Interface Method, invoked when bean is removed * from container. */ @PreDestroy public void destroy() { log .info("AuthenticationFilter has been Removed from the Container."); } /** * Attempt authentication for Asynchronous Request. * * @param request * @param response * @return Authentication - Authentication Object * @throws org.springframework.security.core.AuthenticationException * @throws IOException * @throws ServletException */ @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws org.springframework.security.core.AuthenticationException { // *************************** // Obtain the Parameters for // the Login Attempt. String username = request.getParameter(usernameHeader); String password = request.getParameter(passwordHeader); // ********************************* // Determine if the Authentication // can be allowed due to excessive // failed log-in attempts. // if (securityServiceMonitor.isLoginAttemptAllowed(username, request)) { securityServiceMonitor.monitorLogInAttemptRequest(username, request); log.info("Attempting Authentication for Principal:[" + username + "] for Request Context Path:[" + request.getContextPath() + "]"); SecuritySessionUserObject securitySessionUserObject = new SecuritySessionUserObject(username); securitySessionUserObject.setPassword(password); AuthenticationToken authRequest = new AuthenticationToken(username, securitySessionUserObject); return this.getAuthenticationManager().authenticate(authRequest); } else { throw new AuthenticationException("Unable to allow Authentication Attempt at this time due to excessive Log-In Attempts!"); } } /** * <p/> * successfulAuthentication * Provides Override for additional Successful Authentication Processing. */ @Transactional(rollbackFor = {JSONException.class}) @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException, ServletException { super.successfulAuthentication(request, response, authResult); // *************************************************** // Now get the Session context, so we can initialize // and maintain a Security Service Object. if ((authResult != null) && (authResult.isAuthenticated())) { // ***************************************** // Proceed with Request. log.info("Successful Authentication for Principal:[" + authResult.getPrincipal() + "], of Requested Resource:[" + request.getRequestURI() + "]"); HttpSession session = request.getSession(); session.setAttribute(SecuritySessionUserObject.SECURITY_SESSION_USER_OBJECT_NAME, authResult.getDetails()); if (((SecuritySessionUserObject) authResult.getDetails()).getSecuritySessionProfileObject() != null) { Locale locale = LocaleUtils.toLocale(((SecuritySessionUserObject) authResult.getDetails()).getSecuritySessionProfileObject().getUserLocale()); session.setAttribute("org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE", locale); } // ********************************* // Generate our Response. JSONObject jsonObject = new JSONObject(); try { jsonObject.put("success", true); } catch (JSONException je) { log.error("successfulAuthentication JSONException Encountered:[" + je.getMessage() + "]", je); } HttpServletResponseWrapper responseWrapper = new HttpServletResponseWrapper(response); Writer out = responseWrapper.getWriter(); out.write(jsonObject.toString()); out.close(); // ************************************************** // Indicate Successful Log-in to the Service Monitor. securityServiceMonitor.monitorLogInAttemptRequest(authResult.getPrincipal(), request, true); } } /** * {@inheritDoc} * <p/> * unsuccessfulAuthentication * Provides Override for unsuccessful Authentication Processing. */ @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, org.springframework.security.core.AuthenticationException failed) throws IOException, ServletException { // ************************************** // Obtain the Original Parameters for // the Login Attempt. String username = request.getParameter(usernameHeader); // ********************************* // Provide Failure Response log.info("Unsuccessful Authentication for Principal:[" + username + "], of Requested Resource:[" + request.getContextPath() + "]"); HttpServletResponseWrapper responseWrapper = new HttpServletResponseWrapper(response); Writer out = responseWrapper.getWriter(); JSONObject jsonObject = new JSONObject(); try { jsonObject.put("success", false); jsonObject.put("errors", failed.getMessage()); } catch (JSONException je) { log.error("unsuccessfulAuthentication JSONException Encountered:[" + je.getMessage() + "]", je); } out.write(jsonObject.toString()); out.close(); // ************************************************** // Indicate Successful Log-iin to the Service Monitor. securityServiceMonitor.monitorLogInAttemptRequest(username, request, false); } // ****************************************** // Available Overrides. // ****************************************** /** * Check for a signed_request Parameter from a Service Provider * and Decode the Request. * * @param req * @param res * @param chain * @throws IOException * @throws ServletException */ @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { if (log.isDebugEnabled()) { log.debug("Performing doFilter Request:[" + ((HttpServletRequest) req).getRequestURI() + "]"); } // ************************************************* // Check for a Access_token from an // OAUTH Redirect for Authentication Web Flow. if (((HttpServletRequest) req).getRequestURI().endsWith(OAUTH_REDIRECT)) { // ********************************************************************************** // Step 0, Initial OAUTH Redirect from our JavaScript Code. if (StringUtils.isEmpty(req.getParameter(CODE))) { this.reDirectToServiceProviderCode((HttpServletRequest) req, (HttpServletResponse) res); return; } // ********************************************************************************** // Step 1 Receive Code and Send Request for Access Token, Sending our App Credentials String code = req.getParameter(CODE); // ********************************************************************************** // Step 2 We must Exchange this Code for the Access Token, so perform the // rest call to the service provider. } // End of OAUTH Web Flow // *********************************** // Continue with Filter Chain. super.doFilter(req, res, chain); } /** * Redirect to Service Provider to Obtain authorized Token. * * @param reason - Logging Information, regarding why the Redirect is issued. * @param request * @param response * @throws IOException */ private void reDirectToServiceProviderPermissions(String reason, HttpServletRequest request, HttpServletResponse response) throws IOException { log.info("Redirecting to Allow User to Permit Application rights, Reason:[" + reason + "]"); // TODO : May need to determine the type of Display if using a Handheld Device or not. String redirectUri = request.getRequestURI(); redirectUri = redirectUri.substring(request.getContextPath().length() + 1); String slash = (this.serviceProviderRedirectUrl.endsWith("/")) ? "" : "/"; // ******************************************** // Perform Redirect to Obtain Authorized Token // once the User has Authorized the Application. String url = this.serviceProviderAuthorizeUrl + "?" + "client_id=" + this.serviceProviderAppId + "&" + "redirect_uri=" + this.serviceProviderRedirectUrl + slash + redirectUri + "&" + "scope=" + this.serviceProviderExtendedPermissions + "&" + "display=" + this.serviceProviderPermissionDialogFormDefault; //response.sendRedirect(url); response.setContentType("text/html"); response.getWriter().write("<html><body><script language=\"javascript\">window.open('" + url + "', '_parent', '')</script>" + "<a href=\"" + url + "\">Launch</a> Application.</body></html>"); } /** * Construct Code Request */ private void reDirectToServiceProviderCode(HttpServletRequest request, HttpServletResponse response) throws IOException { // TODO : May need to determine the type of Display if using a Handheld Device or not. // ******************************************** // Perform Redirect to Obtain Authorized Token // once the User has Authorized the Application. response.sendRedirect(this.serviceProviderAuthorizeUrl + "?" + "type=" + "web_server" + "&" + "client_id=" + this.serviceProviderAppId + "&" + "redirect_uri=" + this.host + "/" + OAUTH_REDIRECT + "&" + "scope=" + this.serviceProviderExtendedPermissions + "&" + "display=" + this.serviceProviderPermissionDialogFormDefault); } /** * Construct Access Token Request * * @param code -- OAUTH Initial Code Received to exchange for access token. */ private String constructServiceProviderAccessTokenUrl(String code) { // *************************************************** // Construct Redirect URL to Obtain Authorized Token // once the User has Authorized the Application. // ** Very important the same URL must be used from // ** initial Request, otherwise service provider // ** will not validate request. // return this.serviceProviderAccessTokenUrl + "?" + "client_id=" + this.serviceProviderAppId + "&" + "redirect_uri=" + this.host + "/" + OAUTH_REDIRECT + "&" + "client_secret=" + this.serviceProviderSecret + "&" + "scope=" + this.serviceProviderExtendedPermissions + "&" + "code=" + code; } @Override protected AuthenticationManager getAuthenticationManager() { log.debug("Invoking getAuthenticationManager"); return super.getAuthenticationManager(); } @Override public void setAuthenticationManager (AuthenticationManager authenticationManager) { log.debug("Invoking setAuthenticationManager"); super.setAuthenticationManager(authenticationManager); } @Override public String getFilterProcessesUrl () { log.debug("Invoking getFilterProcessesUrl"); return super.getFilterProcessesUrl(); } @Override public void setAuthenticationFailureHandler (AuthenticationFailureHandler failureHandler) { log.debug("Invoking setAuthenticationFailureHandler"); super.setAuthenticationFailureHandler(failureHandler); } @Override public void setAuthenticationSuccessHandler (AuthenticationSuccessHandler successHandler) { log.debug("Invoking setAuthenticationSuccessHandler"); super.setAuthenticationSuccessHandler(successHandler); } @Override public void setSessionAuthenticationStrategy (SessionAuthenticationStrategy sessionStrategy) { log.debug("Invoking setSessionAuthenticationStrategy"); super.setSessionAuthenticationStrategy(sessionStrategy); } @Override public void setAllowSessionCreation ( boolean allowSessionCreation) { log.debug("Invoking setAllowSessionCreation"); super.setAllowSessionCreation(allowSessionCreation); } @Override protected boolean getAllowSessionCreation () { log.debug("Invoking getAllowSessionCreation"); return super.getAllowSessionCreation(); } @Override public AuthenticationDetailsSource getAuthenticationDetailsSource () { log.debug("Invoking getAuthenticationDetailsSource"); return super.getAuthenticationDetailsSource(); } @Override public void setMessageSource (MessageSource messageSource) { log.debug("Invoking setMessageSource"); super.setMessageSource(messageSource); } @Override public void setAuthenticationDetailsSource (AuthenticationDetailsSource authenticationDetailsSource) { log.debug("Invoking setAuthenticationDetailsSource"); super.setAuthenticationDetailsSource(authenticationDetailsSource); } @Override public void setApplicationEventPublisher (ApplicationEventPublisher eventPublisher) { log.debug("Invoking setApplicationEventPublisher"); super.setApplicationEventPublisher(eventPublisher); } @Override public void setContinueChainBeforeSuccessfulAuthentication ( boolean continueChainBeforeSuccessfulAuthentication) { log.debug("Invoking setContinueChainBeforeSuccessfulAuthentication"); super.setContinueChainBeforeSuccessfulAuthentication(continueChainBeforeSuccessfulAuthentication); } @Override public void setRememberMeServices (RememberMeServices rememberMeServices) { log.debug("Invoking setRememberMeServices"); super.setRememberMeServices(rememberMeServices); } @Override public RememberMeServices getRememberMeServices () { log.debug("Invoking getRememberMeServices"); return super.getRememberMeServices(); } @Override public void setFilterProcessesUrl (String filterProcessesUrl) { log.debug("Invoking setFilterProcessesUrl"); super.setFilterProcessesUrl(filterProcessesUrl); } @Override public void afterPropertiesSet () { log.debug("Invoking afterPropertiesSet"); super.afterPropertiesSet(); } @Override protected boolean requiresAuthentication (HttpServletRequest request, HttpServletResponse response) { log.debug("Invoking requiresAuthentication"); return super.requiresAuthentication(request, response); } }